Тип проекта:
рекомендательный (с элементами исследования)
Данные:
Lifestyle_and_Wellbeing_Data. Данные были взяты с Kaggle, и получены в результате исследования, которое было проведено Центром позитивной психологии в Университете Пенсильвании. Датасет имеет 24 различных переменных, которые отражают физическое и ментальное здоровье респондента, его социальные связи и удовлетворенность работой и в целом жизнью.
Существует оценка, которая отражает, насколько мы преуспеваем как в профессиональной, так и в личной жизни, насколько хорошо человек формирует свой образ жизни, привычки и поведение, чтобы максимизировать общую удовлетворенность жизнью. Наше приложение позволяет получить такую оценку.
Идея: Пользователь отвечает на вопросы анкеты и получает рассчитанный по формуле Института позитивной психологии индекс своего благополучия. Если можно улучшить результаты, то пользователь получает также описание кластера, к которому его отнесли, в котором указаны признаки, по которым в среднем представители данной группы уступают другим группам (пользователю рекомендуется обратить внимание на признаки, к которым его кластер более чувствителен, чтобы улучшить результат). Также пользователю выводится статистическая справка. Был проведен общий анализ датасета, получена “статистическая справка” по респондентам, включенным в датасет. Предсказание индивидуального благополучия идет на основе анкеты более 15000 респондентов. Была восстановлена формула с помощью линейной регрессии, подобрано оптимальное количество кластеров и пронализированы основные отличия между ними для формирования “рекомендаций”
Сценарии: пользователь заполняет анкету - получает рассчитанный индекс благополучия и некоторые рекомендации. Различных сценариев для разных типов пользователей нет.
data = read.csv("~/Desktop/wbi/data/Wellbeing_and_lifestyle_data_Kaggle.csv")
library(dplyr)
find_mode <- function(x) {
u <- unique(x)
tab <- tabulate(match(x, u))
u[tab == max(tab)]
}
df = data %>% group_by(AGE) %>% summarise(FRUITS_VEGGIES = find_mode(FRUITS_VEGGIES),
DAILY_STRESS = find_mode(DAILY_STRESS),
PLACES_VISITED = find_mode(PLACES_VISITED),
CORE_CIRCLE = find_mode(CORE_CIRCLE),
SUPPORTING_OTHERS = find_mode(SUPPORTING_OTHERS),
SOCIAL_NETWORK = find_mode(SOCIAL_NETWORK),
ACHIEVEMENT = find_mode(ACHIEVEMENT),
DONATION = find_mode(DONATION),
BMI_RANGE = find_mode(BMI_RANGE),
TODO_COMPLETED = find_mode(TODO_COMPLETED),
FLOW = find_mode(FLOW),
DAILY_STEPS = find_mode(DAILY_STEPS),
LIVE_VISION = find_mode(LIVE_VISION),
SLEEP_HOURS = find_mode(SLEEP_HOURS),
LOST_VACATION = find_mode(LOST_VACATION),
DAILY_SHOUTING = find_mode(DAILY_SHOUTING),
SUFFICIENT_INCOME = find_mode(SUFFICIENT_INCOME),
PERSONAL_AWARDS = find_mode(PERSONAL_AWARDS),
TIME_FOR_PASSION = find_mode(TIME_FOR_PASSION),
WEEKLY_MEDITATION = find_mode(WEEKLY_MEDITATION))
df = t(df)
df1 = data %>% group_by(GENDER) %>% summarise(FRUITS_VEGGIES = find_mode(FRUITS_VEGGIES),
DAILY_STRESS = find_mode(DAILY_STRESS),
PLACES_VISITED = find_mode(PLACES_VISITED),
CORE_CIRCLE = find_mode(CORE_CIRCLE),
SUPPORTING_OTHERS = find_mode(SUPPORTING_OTHERS),
SOCIAL_NETWORK = find_mode(SOCIAL_NETWORK),
ACHIEVEMENT = find_mode(ACHIEVEMENT),
DONATION = find_mode(DONATION),
BMI_RANGE = find_mode(BMI_RANGE),
TODO_COMPLETED = find_mode(TODO_COMPLETED),
FLOW = find_mode(FLOW),
DAILY_STEPS = find_mode(DAILY_STEPS),
LIVE_VISION = find_mode(LIVE_VISION),
SLEEP_HOURS = find_mode(SLEEP_HOURS),
LOST_VACATION = find_mode(LOST_VACATION),
DAILY_SHOUTING = find_mode(DAILY_SHOUTING),
SUFFICIENT_INCOME = find_mode(SUFFICIENT_INCOME),
PERSONAL_AWARDS = find_mode(PERSONAL_AWARDS),
TIME_FOR_PASSION = find_mode(TIME_FOR_PASSION),
WEEKLY_MEDITATION = find_mode(WEEKLY_MEDITATION))
df1 = t(df1)
Код для статистической справки вставлен с eval = F, так как далее в работе сохраненные значения использоваться не будут. Для начала была написана функция find_mode, которая необходима для поиска моды, то есть наиболее часто встречающегося значения в списке. Далее были построены таблицы с наиболее популярными вариантами ответов по возрастным категориям и полу. Выводы из этих таблиц будут представлены в интерфейсе приложения в качестве текстовой вставки с наиболее интересными и выделяющимися фактами.
Мы загружали уже очищенный датасет, поэтому у нас не было необходимости убирать пропущенные значения (за исключением одной строки с ошибочным значением, из-за которого у нас возникала ошибка при дальнейшей обработке). Также необходимо было убрать генерируемое автоматически время заполнения анкеты, а также возраст и пол респондентов, которые не применяются в формуле индекса благополучия, но нужны для формирования статистической справки о респондентах. Уровень ежедневного стресса автоматически определяется как character, поэтому для дальнейшей работы с моделями переводим в целочисленные значения.
library(readr)
library(dplyr)
##
## Присоединяю пакет: 'dplyr'
## Следующие объекты скрыты от 'package:stats':
##
## filter, lag
## Следующие объекты скрыты от 'package:base':
##
## intersect, setdiff, setequal, union
library(caret)
## Загрузка требуемого пакета: ggplot2
## Загрузка требуемого пакета: lattice
library(tidymodels)
## ── Attaching packages ────────────────────────────────────── tidymodels 1.2.0 ──
## ✔ broom 1.0.5 ✔ rsample 1.2.1
## ✔ dials 1.2.1 ✔ tibble 3.2.1
## ✔ infer 1.0.7 ✔ tidyr 1.3.1
## ✔ modeldata 1.3.0 ✔ tune 1.2.1
## ✔ parsnip 1.2.1 ✔ workflows 1.1.4
## ✔ purrr 1.0.2 ✔ workflowsets 1.1.0
## ✔ recipes 1.0.10 ✔ yardstick 1.3.1
## ── Conflicts ───────────────────────────────────────── tidymodels_conflicts() ──
## ✖ purrr::discard() masks scales::discard()
## ✖ dplyr::filter() masks stats::filter()
## ✖ dplyr::lag() masks stats::lag()
## ✖ purrr::lift() masks caret::lift()
## ✖ yardstick::precision() masks caret::precision()
## ✖ yardstick::recall() masks caret::recall()
## ✖ yardstick::sensitivity() masks caret::sensitivity()
## ✖ yardstick::spec() masks readr::spec()
## ✖ yardstick::specificity() masks caret::specificity()
## ✖ recipes::step() masks stats::step()
## • Dig deeper into tidy modeling with R at https://www.tmwr.org
wb <- read_csv("~/Desktop/wbi/data/Wellbeing_and_lifestyle_data_Kaggle.csv")
## Rows: 15972 Columns: 24
## ── Column specification ────────────────────────────────────────────────────────
## Delimiter: ","
## chr (4): Timestamp, DAILY_STRESS, AGE, GENDER
## dbl (20): FRUITS_VEGGIES, PLACES_VISITED, CORE_CIRCLE, SUPPORTING_OTHERS, SO...
##
## ℹ Use `spec()` to retrieve the full column specification for this data.
## ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
wb <- wb[-10005, ]
wb <- wb %>%
select(-c(Timestamp, AGE, GENDER))
wb$DAILY_STRESS <- as.integer(wb$DAILY_STRESS)
## Warning: в результате преобразования созданы NA
В первую очередь были применены линейная регрессия для восстановления формулы, по которой рассчитывается индекс благополучия, поскольку ее не было в описании к датасету на Kaggle, а первоначальный адрес сайта, на котором можно было пройти анкету, больше не работает. Изначально все равно разделили датасет на обучающую и тестовую выборки.
split = initial_split(wb, prop = 0.8)
set.seed(46)
wb_train = training(split)
wb_test = testing(split)
linmodel <- lm(WORK_LIFE_BALANCE_SCORE ~ ., data = wb_train)
summary(linmodel)
##
## Call:
## lm(formula = WORK_LIFE_BALANCE_SCORE ~ ., data = wb_train)
##
## Residuals:
## Min 1Q Median 3Q Max
## -1.647e-11 -1.500e-13 -3.000e-14 9.000e-14 3.704e-10
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) 5.442e+02 2.685e-13 2.027e+15 <2e-16 ***
## FRUITS_VEGGIES 3.400e+00 2.228e-14 1.526e+14 <2e-16 ***
## DAILY_STRESS -3.400e+00 2.391e-14 -1.422e+14 <2e-16 ***
## PLACES_VISITED 1.700e+00 9.986e-15 1.702e+14 <2e-16 ***
## CORE_CIRCLE 1.700e+00 1.174e-14 1.448e+14 <2e-16 ***
## SUPPORTING_OTHERS 1.700e+00 1.113e-14 1.527e+14 <2e-16 ***
## SOCIAL_NETWORK 1.700e+00 1.069e-14 1.590e+14 <2e-16 ***
## ACHIEVEMENT 1.700e+00 1.301e-14 1.306e+14 <2e-16 ***
## DONATION 3.400e+00 1.783e-14 1.907e+14 <2e-16 ***
## BMI_RANGE -1.700e+01 6.149e-14 -2.764e+14 <2e-16 ***
## TODO_COMPLETED 1.700e+00 1.289e-14 1.319e+14 <2e-16 ***
## FLOW 1.700e+00 1.514e-14 1.123e+14 <2e-16 ***
## DAILY_STEPS 1.900e+00 1.098e-14 1.731e+14 <2e-16 ***
## LIVE_VISION 1.700e+00 1.013e-14 1.679e+14 <2e-16 ***
## SLEEP_HOURS 1.900e+00 2.543e-14 7.471e+13 <2e-16 ***
## LOST_VACATION -1.700e+00 8.225e-15 -2.067e+14 <2e-16 ***
## DAILY_SHOUTING -1.700e+00 1.161e-14 -1.464e+14 <2e-16 ***
## SUFFICIENT_INCOME 1.700e+01 6.941e-14 2.449e+14 <2e-16 ***
## PERSONAL_AWARDS 1.700e+00 1.099e-14 1.547e+14 <2e-16 ***
## TIME_FOR_PASSION 1.700e+00 1.306e-14 1.302e+14 <2e-16 ***
## WEEKLY_MEDITATION 1.700e+00 1.059e-14 1.604e+14 <2e-16 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 3.319e-12 on 12754 degrees of freedom
## (1 пропущенное наблюдение удалено)
## Multiple R-squared: 1, Adjusted R-squared: 1
## F-statistic: 1.176e+29 on 20 and 12754 DF, p-value: < 2.2e-16
predictions <- predict(linmodel, newdata = wb_test)
rmse <- sqrt(mean((wb_test$WORK_LIFE_BALANCE_SCORE - predictions)^2))
mse <- mean((wb_test$WORK_LIFE_BALANCE_SCORE - predictions)^2)
mae <- mean(abs(wb_test$WORK_LIFE_BALANCE_SCORE - predictions))
Значения ошибок RMSE/MAE/MSE близки к нулю. Скорректированный R^2=1 (формула восстановлена, для формулы это нормальные значения). После того как была получена формула, было решено добавить в датасет наблюдения с самым высоким и самым низким возможными результатами, чтобы перевести значения выборки в 100-балльную систему для упрощения интерпретации. После этого мы снова построили линейную регрессию, чтобы получать предсказания именно в такой шкале. Предварительно был удален столбец с оценкой в первоначальной шкале.
wb <- wb %>%
select(-c(WORK_LIFE_BALANCE_SCORE))
#Добавим в таблицу строки с крайними возможными значениями всех признаков, чтобы получить более корректные значения уровня благополучия при скалировании от 0 до 100
happiest <- list(FRUITS_VEGGIES = 5, DAILY_STRESS = 0, PLACES_VISITED = 10,
CORE_CIRCLE = 10, SUPPORTING_OTHERS = 10, SOCIAL_NETWORK = 10, ACHIEVEMENT = 10,
DONATION = 5, BMI_RANGE = 1, TODO_COMPLETED = 10, FLOW = 10, DAILY_STEPS = 10,
LIVE_VISION = 10, SLEEP_HOURS = 10, LOST_VACATION = 0, DAILY_SHOUTING = 0,
SUFFICIENT_INCOME = 2, PERSONAL_AWARDS = 10, TIME_FOR_PASSION = 10,
WEEKLY_MEDITATION = 10)
unhappiest <- list(FRUITS_VEGGIES = 0, DAILY_STRESS = 5, PLACES_VISITED = 0,
CORE_CIRCLE = 0, SUPPORTING_OTHERS = 0, SOCIAL_NETWORK = 0, ACHIEVEMENT = 0,
DONATION = 0, BMI_RANGE = 2, TODO_COMPLETED = 0, FLOW = 0, DAILY_STEPS = 1,
LIVE_VISION = 0, SLEEP_HOURS = 1, LOST_VACATION = 10, DAILY_SHOUTING = 10,
SUFFICIENT_INCOME = 1, PERSONAL_AWARDS = 0, TIME_FOR_PASSION = 0,
WEEKLY_MEDITATION = 0)
wb <- rbind(wb, happiest)
wb <- rbind(wb, unhappiest)
wb_scores=predict(linmodel, wb)
scores_vector=as.vector(wb_scores)
df=data.frame(scores_vector=scores_vector)
wb=cbind(wb, df)
wb$wb_scores_scaled <- 100*((df$scores_vector - 480) / (820.2 - 480))
lm <- lm(wb_scores_scaled ~ FRUITS_VEGGIES + DAILY_STRESS + PLACES_VISITED + CORE_CIRCLE + SUPPORTING_OTHERS + SOCIAL_NETWORK + ACHIEVEMENT + DONATION + BMI_RANGE + TODO_COMPLETED + FLOW + DAILY_STEPS + LIVE_VISION + SLEEP_HOURS + LOST_VACATION + DAILY_SHOUTING + SUFFICIENT_INCOME + PERSONAL_AWARDS + TIME_FOR_PASSION + WEEKLY_MEDITATION, data = wb)
summary(lm)
##
## Call:
## lm(formula = wb_scores_scaled ~ FRUITS_VEGGIES + DAILY_STRESS +
## PLACES_VISITED + CORE_CIRCLE + SUPPORTING_OTHERS + SOCIAL_NETWORK +
## ACHIEVEMENT + DONATION + BMI_RANGE + TODO_COMPLETED + FLOW +
## DAILY_STEPS + LIVE_VISION + SLEEP_HOURS + LOST_VACATION +
## DAILY_SHOUTING + SUFFICIENT_INCOME + PERSONAL_AWARDS + TIME_FOR_PASSION +
## WEEKLY_MEDITATION, data = wb)
##
## Residuals:
## Min 1Q Median 3Q Max
## -1.888e-10 -2.600e-14 1.200e-14 5.100e-14 7.988e-12
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) 1.887e+01 1.088e-13 1.735e+14 <2e-16 ***
## FRUITS_VEGGIES 9.994e-01 9.020e-15 1.108e+14 <2e-16 ***
## DAILY_STRESS -9.994e-01 9.688e-15 -1.032e+14 <2e-16 ***
## PLACES_VISITED 4.997e-01 4.044e-15 1.236e+14 <2e-16 ***
## CORE_CIRCLE 4.997e-01 4.762e-15 1.049e+14 <2e-16 ***
## SUPPORTING_OTHERS 4.997e-01 4.493e-15 1.112e+14 <2e-16 ***
## SOCIAL_NETWORK 4.997e-01 4.335e-15 1.153e+14 <2e-16 ***
## ACHIEVEMENT 4.997e-01 5.305e-15 9.419e+13 <2e-16 ***
## DONATION 9.994e-01 7.221e-15 1.384e+14 <2e-16 ***
## BMI_RANGE -4.997e+00 2.488e-14 -2.009e+14 <2e-16 ***
## TODO_COMPLETED 4.997e-01 5.196e-15 9.617e+13 <2e-16 ***
## FLOW 4.997e-01 6.132e-15 8.150e+13 <2e-16 ***
## DAILY_STEPS 5.585e-01 4.445e-15 1.256e+14 <2e-16 ***
## LIVE_VISION 4.997e-01 4.105e-15 1.217e+14 <2e-16 ***
## SLEEP_HOURS 5.585e-01 1.027e-14 5.437e+13 <2e-16 ***
## LOST_VACATION -4.997e-01 3.334e-15 -1.499e+14 <2e-16 ***
## DAILY_SHOUTING -4.997e-01 4.695e-15 -1.064e+14 <2e-16 ***
## SUFFICIENT_INCOME 4.997e+00 2.806e-14 1.781e+14 <2e-16 ***
## PERSONAL_AWARDS 4.997e-01 4.446e-15 1.124e+14 <2e-16 ***
## TIME_FOR_PASSION 4.997e-01 5.306e-15 9.417e+13 <2e-16 ***
## WEEKLY_MEDITATION 4.997e-01 4.280e-15 1.168e+14 <2e-16 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 1.503e-12 on 15951 degrees of freedom
## (1 пропущенное наблюдение удалено)
## Multiple R-squared: 1, Adjusted R-squared: 1
## F-statistic: 6.204e+28 on 20 and 15951 DF, p-value: < 2.2e-16
Сначала еще раз для проверки убираем пропущенные значения, которые могли не заметить, и переименовываем (в одной из версий было переименование в wb_dummies, которое и дальше используется в работе, поэтому оставляем название для удобства)
wb_dummies=na.omit(wb)
После этого обратимся к методу локтя. Перебирали значения до 10, поскольку это достаточно ресурсоемкий процесс.
library(stats)
library(ggplot2)
clusters_inertia <- numeric()
for (n_clusters in 2:10) {
kmeans_model <- kmeans(wb_dummies, centers = n_clusters, nstart = 25)
clusters_inertia <- c(clusters_inertia, kmeans_model$tot.withinss)
}
ggplot(data.frame(num_clusters = 2:10, inertia = clusters_inertia), aes(x = num_clusters, y = inertia)) +
geom_line() +
labs(title = "Кластеры - проверяем методом локтя", x = "Количество кластеров", y = "Инерция") +
theme_minimal()
Наибольший перегиб на двух, но кажется, что заметное “снижение” идет после 4, однако график специфичный, надо проверить и другим способом. Мы остановились на silhouette score. Нам в данном случае необходимо обратить внимание именно на наибольший показатель.
library(cluster)
clusters_silhouette <- numeric()
for (n_clusters in 2:10) {
kmeans_model <- kmeans(wb_dummies, centers = n_clusters, nstart = 25)
silhouette <- silhouette(kmeans_model$cluster, dist(wb_dummies))
clusters_silhouette <- c(clusters_silhouette, mean(silhouette[, "sil_width"]))
}
ggplot(data.frame(num_clusters = 2:10, silhouette = clusters_silhouette), aes(x = num_clusters, y = silhouette)) +
geom_line() +
geom_point() +
labs(title = "Silhouette score - ориентируемся на максимальные значения, но больше двух", x = "Количество кластеров", y = "Средний silhouette score") +
theme_minimal()
Наибольшее значение для 2 и далее уменьшается с увеличением числа кластеров. Так как в нашем случае 2 кластера - не показательно, поскольку это может сделать описания дихотомичными. Поэтому мы решили остановить выбор на 3 или 4 кластерах. Далее будет рассмотрена процедура выбора между 3 или 4 кластерами на основе анализа средних значений групп.
Проанализируем ситуацию с тремя группами.
#Приведём все наблюдения к 10-балльной шкале, умножив на 2 те, где по 5-балльной
group3$FRUITS_VEGGIES = group4$FRUITS_VEGGIES*2
group3$DAILY_STRESS = group4$DAILY_STRESS*2
group3$DONATION = group4$DONATION*2
group3$BMI_RANGE = group4$BMI_RANGE*5
group3$SUFFICIENT_INCOME = group4$SUFFICIENT_INCOME*5
Group_1 = filter(group3, Group == 1)
Group_2 = filter(group3, Group == 2)
Group_3 = filter(group3, Group == 3)
Gr_1 = colMeans(Group_1)
Gr_2 = colMeans(Group_2)
Gr_3 = colMeans(Group_3)
data3 = data.frame(Gr_1, Gr_2, Gr_3)
mean3 = colMeans(group3)
data3 = mutate(data3, Mean = mean3)
Differ3 = data.frame(M_1 = data3$Gr_1[1:21] - data3$Mean[1:21], M_2 = data3$Gr_2[1:21] - data3$Mean[1:21], M_3 = data3$Gr_3[1:21] - data3$Mean[1:21])
Differ_abs3 = abs(Differ3)
Как видим, здесь группы просто поделены по принципу плохо/средне/хорошо. То есть, можно сказать, что группа Gr_1 – это просто почти средние показатели, Gr_2 – те, у кого показатели ниже среднего, Gr_3 – у кого выше среднего. Анализировать такое распределение групп не очень интересно и полезно, поэтому перейдём к распределению на 4 группы.
Анализируем ситуацию с четырьмя группами.
#Приведём все наблюдения к 10-балльной шкале, умножив на 2 те, где по 5-балльной
group4$FRUITS_VEGGIES = group4$FRUITS_VEGGIES*2
group4$DAILY_STRESS = group4$DAILY_STRESS*2
group4$DONATION = group4$DONATION*2
group4$BMI_RANGE = group4$BMI_RANGE*5
group4$SUFFICIENT_INCOME = group4$SUFFICIENT_INCOME*5
Group_11 = filter(group4, Group == 1)
Group_21 = filter(group4, Group == 2)
Group_31 = filter(group4, Group == 3)
Group_41 = filter(group4, Group == 4)
Gr_11 = colMeans(Group_11)
Gr_21 = colMeans(Group_21)
Gr_31 = colMeans(Group_31)
Gr_41 = colMeans(Group_41)
data4 = data.frame(Gr_11, Gr_21, Gr_31, Gr_41)
Groups4 = data.frame(Bad = Gr_31[1:21], Satisfactory = Gr_11[1:21], Good = Gr_41[1:21], Excellent = Gr_21[1:21])
Посчитаем среднее значение каждого показателя и посмотрим на отклонения от среднего для каждого показателя для каждой группы.
mean = colMeans(group4)
Groups4 = mutate(Groups4, Mean = mean[1:21])
Differ = data.frame(B_M = Groups4$Bad - Groups4$Mean, S_M = Groups4$Satisfactory - Groups4$Mean, G_M = Groups4$Good - Groups4$Mean, E_M = Groups4$Excellent - Groups4$Mean)
Differ_abs = abs(Differ)
У группы “Bad” наибольшее отклонение от среднего можно увидеть по показателям PLACES_VISITED, SUPPORTING_OTHERS, DONATION, TODO_COMPLETED. У группы “Satisfactory” наибольшее отклонение от среднего можно увидеть по показателям SUPPORTING_OTHERS, PLACES_VISITED, LIVE_VISION, TIME_FOR_PASSION У группы “Good” наибольшее отклонение от среднего можно увидеть по показателям PLACES_VISITED, DONATION, SUPPORTING_OTHERS, TODO_COMPLETED У группы “Excellent” наибольшее отклонение от среднего можно увидеть по показателям SUPPORTING_OTHERS, PLACES_VISITED, ACHIEVEMENT, DONATION
Видим, что каждую группу в общем наиболее выделяют такие показатели как PLACES_VISITED, SUPPORTING_OTHERS, DONATION.
Первоначально мы получили такие описания, но потом обнаружили, что при кластеризации использовался столбец с индексом (который коррелировал с остальными столбцами), поэтому после его исключения на основе уже полученных описаний был ещё раз проведен анализ.
set.seed(123)
kmeans_modelka <- kmeans(wb_dummies, centers = 4, nstart = 10)
cluster_labels <- kmeans_modelka$cluster
group4 = mutate(wb_dummies, Group = cluster_labels)
#Приведём все наблюдения к 10-балльной шкале, умножив на 2 те, где по 5-балльной
group4$FRUITS_VEGGIES = group4$FRUITS_VEGGIES*2
group4$DAILY_STRESS = group4$DAILY_STRESS*2
group4$DONATION = group4$DONATION*2
group4$BMI_RANGE = group4$BMI_RANGE*5
group4$SUFFICIENT_INCOME = group4$SUFFICIENT_INCOME*5
Group_11 = filter(group4, Group == 1)
Group_21 = filter(group4, Group == 2)
Group_31 = filter(group4, Group == 3)
Group_41 = filter(group4, Group == 4)
Gr_11 = colMeans(Group_11)
Gr_21 = colMeans(Group_21)
Gr_31 = colMeans(Group_31)
Gr_41 = colMeans(Group_41)
data4 = data.frame(Gr_11, Gr_21, Gr_31, Gr_41)
Groups4 = data.frame(Bad = Gr_21[1:21], Satisfactory = Gr_11[1:21], Good = Gr_31[1:21], Excellent = Gr_41[1:21])
Посчитаем среднее значение каждого показателя и посмотрим на отклонения от среднего для каждого показателя для каждой группы.
mean = colMeans(group4)
Groups4 = mutate(Groups4, Mean = mean[1:21])
Differ = data.frame(B_M = Groups4$Bad - Groups4$Mean, S_M = Groups4$Satisfactory - Groups4$Mean, G_M = Groups4$Good - Groups4$Mean, E_M = Groups4$Excellent - Groups4$Mean)
Differ_abs = abs(Differ)
Характеристики кластеров:
Кластер 1 У этого кластера почти по всем показателям у этой группы самые плохие результаты. Наибольшее отклонение от среднего наблюдается в таких характеристиках как: DONATION, SUFFICIENT_INCOME, SUPPORTING_OTHERS, FRUITS_VEGGIES.
На что нужно обратить внимание 1 кластеру: В первую очередь: насколько часто Вы жертвуете своё время или деньги для благих целей (DONATION), хватает ли вам дохода для удовлетворения базовых потребностей (SUFFICIENT_INCOME). Во вторую очередь: скольким людям Вы помогаете достичь лучшей жизни (SUPPORTING_OTHERS), сколько фруктов и овощей Вы едите каждый день (FRUITS_VEGGIES)
Кластер 2 Среди особенностей этого кластера можно выделить: наименьшее кол-во пропущенных отпусков (LOST_VACATION), самый низкий ИМТ (BMI_RANGE). Также у этой группы пусть и не самые, но довольно высокие показатели удовлетворенности доходом, кол-ва новых посещенных мест, потребляемых фруктов и овощей, медитаций и т.д.
Среди западающих показателей: TIME_FOR_PASSION, ACHIEVEMENT, LIVE_VISION, SUPPORTING_OTHER.
На что нужно обратить внимание 2 кластеру: В первую очередь: сколько часов Вы проводите ежедневно, занимаясь тем, чем Вы увлекаетесь (TIME_FOR_PASSION), сколькими значительными достижениями Вы гордитесь (ACHIEVEMENT) Во вторую очередь: на сколько лет вперёд Ваше видение жизни Вам ясно (LIVE_VISION), скольким людям Вы помогаете достичь лучшей жизни (SUPPORTING_OTHERS).
Кластер 3 Этот кластер можно охарактеризовать как наиболее приближенный к идеальному, так как почти по всем показателям он является лучшим.
У этого кластера следующие особенности: самый низкий уровень стресса, негативных эмоций, большее кол-во часов сна, количество пройденных шагов, медитаций, кол-во сделанных дел, социальных взаимодействий, поддержки окружающих пожертвований и т.д.
Кластер 4 У этого кластера довольно средние показатели относительно других групп, однако можно выделить наименьшее кол-во часов сна, самый высокий уровень стресса и наибольшее кол-во потерянных отпускных дней.
Среди западающих показателей: LOST_VACATION, DAILY_STRESS, SUFFICIENT_INCOME, PLACES_VISITED.
На что нужно обратить внимание 4 кластеру: В первую очередь: сколько дней отпуска Вы обычно теряете каждый год (LOST_VACATION), какой уровень стресса вы обычно испытываете каждый день (DAILY_STRESS).
Во вторую очередь: хватает ли вам дохода для удовлетворения базовых потребностей (SUFFICIENT_INCOME), сколько новых мест Вы посещаете (PLACES_VISITED).
Предобработка изначальных данных (датасета) и построение модели происходит в приложении, после чего на основе ввода пользователя ему предсказывается результат и он (пользователь) относится к одному из кластеров.
Входные данные:
Ответы на вопросы анкеты (20 вопросов, два бинарных вопроса, 18 слайдеров).
Выходные данные:
Числовое значение индекса благополучия, текстовая оценка результата и текстовая рекомендации (в соответствии с кластером, в который попадает пользователь).
Небольшая текстовая статистическая справка о том, как исходя из датасета отвечают люди разного пола и возраста.
Скрин интерфейса:
Код интерфейса и серверной части:
ui <- tags$div(
style = "display: flex; flex-direction: column; justify: space-between; height: 100vh; width: 100%;",
fluidPage(
setBackgroundImage(src = "https://images.unsplash.com/photo-1516541196182-6bdb0516ed27?q=80&w=2574&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D"),
tags$head(
tags$style(HTML("
#main-content { display: flex; flex-direction: column; }
#content-body { overflow-y: auto; } # Allows content to scroll
body {margin: 0; width: 100%; padding: 0; opacity:0.5;}
.container-fluid {margin:0; width:100%; min-height:100vh; padding: 0;}
#summary {width:100%; font-size:60px; text-align:center;}
.recommendation-text {
white-space: pre-wrap;
word-wrap: break-word;
width: 100%;
display: flex; flex-direction: column;
gap: 0;}
.form-group {
width: 100% !important;
}
.shiny-input-container .irs {
width: 100% !important;
}
.shiny-slider-input .irs {
width: 100% !important;
}
.shiny-input-radiogroup {
width: 100%;
}
.equal-height-columns {
display: flex;
justify-content: space-between;
}
.equal-height-columns > div {
height:100%;
flex: 1;
display: flex;
flex-direction: column;
border:none;
}
.equal-height-columns > div > div {
padding: 7px;
border: 2px solid #94B1FF;
border-radius: 5px;
background-color:rgba(148,177,255,0.08);
}
@media(max-width:35em){
h1 {
font-size: 15px;
}
a {
font-size: 8px;
}
}
@media (max-width: 600px) {
.equal-height-columns {
flex-direction: column;
}}
"))
),
div(
id = "main-content",
class = 'bg-img',
useShinyjs(),
tags$header(
style = "display: flex; align-items: center; justify-content: center;margin-bottom: 40px; background-color: #B4CFFA; width: 100%; padding-left: 10px; padding-right: 10px;border-radius: 72% 28% 72% 28% / 23% 77% 23% 77%; border: 7px solid #B4CFFA; box-shadow: 0px 0px 12px 5px #B4CFFA",
div(
style = "display: flex; align-items: center;",
tags$h1("Индекс благополучия", style = "max-width: fit-content; "),
actionLink("surveyLink", "Основано на результатах опроса Authentic Happiness Project", onclick = "window.open('https://www.kaggle.com/datasets/ydalat/lifestyle-and-wellbeing-data');", style = " ")
)
),
div(
id = "content-body",
div(id = "introText",
h3("Узнайте свой уровень благополучия"),
p("Нажмите кнопку ниже чтобы начать опрос."),
actionButton("startSurvey", "Начать опрос", class = "btn-primary btn-lg"),
style = "text-align: center; padding: 20px; width:100%;"
),
conditionalPanel(
condition = "input.startSurvey > 0",
div(id = "formDiv",
style = 'padding:10px; flex-grow:0;',
div(class = "equal-height-columns",
column(4,
style = "padding: 5px; margin-bottom: 10px; height:100%;",
sliderInput("fruits", "1) Какое количество фруктов/овощей Вы потребляете каждый день?", min = 0, max = 5, value = 0),
sliderInput("stress", "2) Какой уровень стресса вы обычно испытываете каждый день?", min = 0, max = 5, value = 0),
sliderInput("newPlaces", "3) Как много новых мест вы посетили за последний год?", min = 0, max = 10, value = 0),
sliderInput("closePeople", "4) Сколько людей входит в ваш близкий круг общения?
", min = 0, max = 10, value = 0),
sliderInput("helpOthers", "5) Скольким людям вы помогли улучшить жизнь за последний год?", min = 0, max = 10, value = 0),
sliderInput("interactPeople", "6) С каким кол-вом людей вы обычно контактируете в течение дня?", min = 0, max = 10, value = 0),
sliderInput("achievements", "7) Сколькими своими значительными достижениями за последний год Вы гордитесь?
", min = 0, max = 10, value = 0)),
column(4,
style = "padding: 5px; margin-bottom: 10px; height:100%;",
sliderInput("donate", "8) Сколько раз за последний год вы жертвовали деньгами или своим временем на благотворительность?", min = 0, max = 5, value = 0),
sliderInput("shout", "16) Сколько раз за неделю обычно вы кричите или обижаетесь на кого-то?", min = 0, max = 10, value = 0),
sliderInput("tasks", "10) Насколько хорошо вы справляетесь с выполнением своего списка дел на неделю?", min = 0, max = 10, value = 0),
sliderInput("flowHours", "11) Сколько часов в день обычно вы находитесь в состоянии потока, т.е. полностью погружены в какую-то деятельность?", min = 0, max = 10, value = 0),
sliderInput("walkSteps", "12) Сколько тысяч шагов вы обычно проходите каждый день?", min = 1, max = 10, value = 1),
sliderInput("visionYears", "13) На сколько лет вперед у вас есть четкое представление о своей жизни?", min = 0, max = 10, value = 0),
sliderInput("sleep", "14) Сколько часов в день Вы обычно спите?
", min = 1, max = 10, value = 1)),
column(4,
style = "padding: 5px; margin-bottom: 10px; height:100%;",
sliderInput("vacationLoss", "15) Сколько дней отпуска вы обычно теряете каждый год?", min = 0, max = 10, value = 0),
radioButtons(
"bmi",
"9) Какой у Вас индекс массы тела (ИМТ)? (ИМТ = вес/(рост^2))
",
choices = list(
"меньше 25" = 1,
"25 и больше" = 2
),selected = NA),
radioButtons(
"incomeSufficiency",
"17) Вам хватает дохода, чтобы обеспечить свои базовые потребности?",
choices = list(
"не хватает" = 1,
"хватает" = 2
),selected = NA),
sliderInput("recognitions", "18) Сколько наград вы получили в своей жизни?", min = 0, max = 10, value = 0),
sliderInput("passionHours", "19) Сколько часов в день вы проводите, занимаясь тем, что вам действительно нравится?", min = 0, max = 10, value = 0),
sliderInput("selfThink", "20) Сколько раз в неделю у вас есть возможность подумать о себе?", min = 0, max = 10, value = 0),
actionButton("submit", "Отправить", class = "btn-primary btn-lg"))
)
)
),
conditionalPanel(
condition = "input.submit > 0 && output.validInputs",
div(id = "resultDiv",
style = 'padding-left:10px;padding-right:10px; width:100%;overflow-x: hidden; display: flex; flex-direction:column;',
div(style='display:flex; justify-content:space-between;',
div(style = 'max-width:60%; font-size:15px; text-shadow: 1px 1px 0px rgba(138,163,206,0.54); background-color:rgba(189, 223, 255, 0.3); padding: 10px; border-radius:15px;',
h4("Небольшая статистическая справка:"),
textOutput("loadedText")),
div(style='margin-right: 10px; align-self: center; max-width:fit-content; text-align:center; font-size:30px; flex-grow:1; block-size: fit-content; padding-left:20px;padding-right:20px; border-radius: 25px; background-color: rgba(148,177,255,0.38);box-shadow: 0px 0px 20px 3px rgba(148,177,255,0.5);',
div('Результат:'),
textOutput("summary"))
) ,
div(class = "recommendation-text",id='recom-text',
style='text-shadow: 1px 1px 0px rgba(138,163,206,0.54);',
h4("Наши рекомендации"),
uiOutput("recom")
)
)
)
)
)
)
)
server <- function(input, output, session) {
validInputs <- reactive({
!is.null(input$incomeSufficiency) && !is.na(input$incomeSufficiency) &&
!is.null(input$bmi) && !is.na(input$bmi)
})
output$validInputs <- reactive({
validInputs()
})
outputOptions(output, "validInputs", suspendWhenHidden = FALSE)
observeEvent(input$submit, {
if (!validInputs()) {
showModal(modalDialog(
title = "Неправильно заполнена форма",
"Для отправки нужно заполнить все поля. Ответьте на пропущенные вопросы и отправьте форму заново.",
easyClose = TRUE
))
} else {
new_data <- data.frame(
FRUITS_VEGGIES = as.integer(input$fruits),
DAILY_STRESS = as.integer(input$stress),
PLACES_VISITED = as.integer(input$newPlaces),
CORE_CIRCLE = as.integer(input$closePeople),
SUPPORTING_OTHERS = as.integer(input$helpOthers),
SOCIAL_NETWORK = as.integer(input$interactPeople),
ACHIEVEMENT = as.integer(input$achievements),
DONATION = as.integer(input$donate),
BMI_RANGE = as.integer(input$bmi),
TODO_COMPLETED = as.integer(input$tasks),
FLOW = as.integer(input$flowHours),
DAILY_STEPS = as.integer(input$walkSteps),
LIVE_VISION = as.integer(input$visionYears),
SLEEP_HOURS = as.integer(input$sleep),
LOST_VACATION = as.integer(input$vacationLoss),
DAILY_SHOUTING = as.integer(input$shout),
SUFFICIENT_INCOME = as.integer(input$incomeSufficiency),
PERSONAL_AWARDS = as.integer(input$recognitions),
TIME_FOR_PASSION = as.integer(input$passionHours),
WEEKLY_MEDITATION = as.integer(input$selfThink)
)
print("New Data for Prediction:")
print(new_data)
print(colnames(wb_dummies))
print(colnames(new_data))
print(ncol(wb_dummies))
print(ncol(new_data))
result <- predict(lm, newdata = new_data)
updated_data <- rbind(wb_dummies, new_data)
kmeans_model <- kmeans(updated_data, centers = 4, nstart = 10)
cluster_labels <- kmeans_model$cluster
updated_data$cluster_labels <- cluster_labels
user_cluster <- updated_data$cluster_labels[nrow(updated_data)]
print(user_cluster)
print(class(result))
recommendations <- switch(as.character(user_cluster),
"1" = "Представители данного кластера по сравнению с другими реже жертвуют свое время или деньги на благие цели. Также им реже хватает дохода для удовлетворения своих базовых потребностей. Более того, они не так часто помогают людям в своем окружении и едят меньше фруктов и овощей, чем представители других групп.",
"2" = "Среди особенностей этого кластера можно выделить: наименьшее кол-во пропущенных отпусков, самый низкий ИМТ. Также у этой группы пусть и не самые, но довольно высокие показатели удовлетворенности доходом, кол-ва новых посещенных мест, потребляемых фруктов и овощей, медитаций и т.д. Однако в среднем они меньше времени по сравнению с остальными ежедневно уделяют своим увлечениям, гордятся меньшим количеством собственных достижений, реже поддерживают близких и видят свое будущее на меньшее число лет вперед.",
"3" = "У этого кластера в среднем самый низкий уровень стресса, негативных эмоций, большее кол-во часов сна, количество пройденных шагов, медитаций, кол-во сделанных дел, социальных взаимодействий, поддержки окружающих пожертвований и т.д.",
"4" = "Данному кластеру чаще остальных надо следить за уровнем ежедневного стресса, посещать больше новых мест. Также представители чаще сталкиваются с проблемой нехватки денег и чаще теряют дни отпуска.",
"Ошибка в кластеризации")
if (result <= 40) {
recommendation <- paste("У вас очень низкий уровень благополучия. Чтобы улучшить свой результат, вы можете обратить внимание на критерии, к которым чаще остальных уязвимы представители той же группы:\n", recommendations)
recommendation_bg <- "rgba(255, 141, 141, 0.3)"
} else if (result > 40 && result <= 60) {
recommendation <- paste("У вас средний уровень благополучия. Есть куда расти! Чтобы улучшить свой результат, вы можете обратить внимание на критерии, к которым чаще остальных уязвимы представители той же группы:\n", recommendations)
recommendation_bg <- "rgba(176, 211, 255, 0.3)"
} else if (result > 60 && result <= 80) {
recommendation <- paste("Прекрасный результат, не забывайте его поддерживать. Чтобы улучшить свой результат, вы можете обратить внимание на критерии, к которым чаще остальных уязвимы представители той же группы:\n", recommendations)
recommendation_bg <- "rgba(193, 254, 197, 0.3)"
} else if (result > 80) {
recommendation <- "Вау, поздравляем с таким высоким результатом! Продолжайте в том же духе"
recommendation_bg <- "rgba(193, 254, 197, 0.35)"
} else {
recommendation <- "Ошибка в выводе данных"
recommendation_bg <- "rgba(200, 15, 15, 0.4)"
}
print("Recommendations:")
print(recommendation)
shinyjs::hide("formDiv")
output$summary <- renderText({
paste(as.integer(result))
})
output$recom <- renderUI({
tags$div(
style = paste("background-color:", recommendation_bg, "; padding:10px; border-radius: 15px;"),
recommendation
)
})
output$loadedText <- renderText({
'
Респонденты до 20 лет реже поддерживают других и занимаются благотворительностью, но больше времени уделяют увлечениям. Группа 21-35 лет имеет самый узкий круг общения, в среднем 5 человек. Наименее уверены в будущем респонденты 21-35 и 36-50 лет, тогда как у респондентов старше 51 года уверенность выше. Респонденты старше 51 года также едят больше фруктов и овощей. Существенных различий между гендерами нет, однако женщины более уверены в будущем и продуктивны, но гордятся меньшим количеством событий в своей жизни.'
})
}})
observeEvent(input$startSurvey, {
hide("introText")
})
}
Экспертное оценивание:
Экспертами являются участники команды. Мы сами вводили различные варианты ответов и проверяли адекватность вывода результата. Оценивание по метрикам: Метрики оценки качества линейной регрессии: RMSE, MSE, MAE, adjusted R2
Вопрос: Какие по смысловому наполнению у вас получились кластеры?
Кластеры различаются по уровню благосостояния и условно их можно разделить на “плохой”, “удовлетворительный”, “хороший”, “отличный”. Если описывать более подробно, то можно выделить следующие особенности. Кластер 1. У этого кластера почти по всем показателям у этой группы самые плохие результаты. Наибольшее отклонение от среднего наблюдается в таких характеристиках как: DONATION, SUFFICIENT_INCOME, SUPPORTING_OTHERS, FRUITS_VEGGIES. Таким образом это самая далекая от идеала группа. Кластер 2. Среди особенностей этого кластера можно выделить: наименьшее кол-во пропущенных отпусков (LOST_VACATION), самый низкий ИМТ (BMI_RANGE). Также у этой группы пусть и не самые, но довольно высокие показатели удовлетворенности доходом, кол-ва новых посещенных мест, потребляемых фруктов и овощей, медитаций и т.д. Кластер 3. Этот кластер можно охарактеризовать как наиболее приближенный к идеальному, так как почти по всем показателям он является лучшим. У этого кластера следующие особенности: самый низкий уровень стресса, негативных эмоций, большее кол-во часов сна, количество пройденных шагов, медитаций, кол-во сделанных дел, социальных взаимодействий, поддержки окружающих пожертвований и т.д. Таким образом это самый близкий к идеалу кластер. Кластер 4. У этого кластера довольно средние показатели относительно других групп, однако можно выделить наименьшее кол-во часов сна, самый высокий уровень стресса и наибольшее кол-во потерянных отпускных дней. Среди западающих показателей: LOST_VACATION, DAILY_STRESS, SUFFICIENT_INCOME, PLACES_VISITED. Несмотря на в общем удовлетворительные показатели высокий уровень стресса указывает на то, что эта благосостояние этого кластера ниже среднего.
Ответ:
Вопрос: Почему во всех вопросах сделали “ползунок”, а не ввод числа в свободной форме? Хотелось бы понять , почему ползунки ограничены для получения наград за жизнь например, как будто у человека их может быть больше, вопрос в ограничении в некоторых вопросах.
Ответ: В проекте использовалась готовая анкета с готовой шкалой, использование ввода в свободной форме привело бы к неверному предсказанию результата.
Вопрос: Пробовала ли команда ввести данные некоторых людей из изначального датасета, чтобы сравнить общую оценку, которую им поставила их система, с той оценкой, которая изначально была дана в датасете?
Ответ: Для сравнения результатов предсказания и первоначальных значений нами были рассчитаны MSE, RMSE, MAE, отдельная проверка реальных значений и предсказаний была осуществлена для максимально и минимально возможных результатов теста.
Вопрос: На основе чего формулировались вопросы для анкеты, были ли для этого использованы литературные источники или просто на основании датасета ? Как именно писалась рекомендация для пользователя?
Ответ: Вопросы были взяты из изначальной анкеты на основе исследований Центра позитивной психологии Университета Пенсильвании. Данная анкета уже использовалась для сбора статистики (датасет, используемый нами).
Вопрос: Что подразумевается под «группами, полученными при кластеризации», для которых выделяются признаки? По какому принципу формируются эти группы?
Ответ: Была проведена кластеризация (k-means). Кластеризация – разбиение на группы.
Вопрос: Как вы думаете, можно ли было бы реализовать ваше приложение, если бы существовала возможность пользовательского ввода, которая не предполагает какого-то конкретного жанра, т.е. такие случаи, когда пользователь не знает или не может точно определить жанр игры, либо не имеет доступа к необходимой информации?
Ответ: Этот вопрос попал к нам ошибочно, речь здесь идёт про другой проект.
Вопрос: Если у пользователя средний уровень благополучия, но при этом какие-то показатели на максимальном уровне, а какие то на минимальном, получит ли он рекомендации именно по тем пунктам, которые у него просели либо рекомендации общие для всех людей, получивших средний результат?
Ответ: Пользователь получит рекомендации на основе того, к какому кластеру его показатели будут наиболее близки. Поскольку некоторые характеристики являются более значимыми для распределения в определенную группу, ответ на этот вопрос зависит от признаков, у которых максимальный/минимальный балл. Поэтому при формировании рекомендации для конкретного респондента рекомендуется именно обратить внимание на более “чувствительные” для его группы характеристики - описание идет именно в среднем по кластеру по сравнению с остальными группами.
Вопрос: Сколько вышло кластеров, по итогам которым давались рекомендации? Интересно, насколько варьируются рекомендации в зависимости от полученных категорий ответов, и следовательно, насколько они будут подходить для пользователей.
Ответ: При расчетах было определено, что в нашем случае оптимально использование 4 кластеров на основе silhouette score и метода локтя. Как было указано выше, представляется описание кластера, к которому ответы пользователя оказались наиболее близки. Если пользователь получает очень высокую оценку, то ему не даются конкретные рекомендации. Если общую оценку благополучия можно улучшить, то пользователю предлагается обратить внимание на наиболее “чувствительные” (значимые) для его группы характеристики, по которым группа, к которой его отнесли, имеет более низкие по сравнению с остальными значения.
Вопрос: Формулировки у некоторых вопросов неоднозначные. В том же вопросе о фруктах, там спрашивает про количество штук? Обычно ведь спрашивают в граммах
Ответ: В изначальной анкете спрашивается про количество штук, поэтому была сохранена данная формулировка.
Вопрос: Есть вопрос к оценке разработанной системы: пробовала ли команда ввести данные некоторых людей из изначального датасета, чтобы сравнить общую оценку, которую им поставила их система, с той оценкой, которая изначально была дана в датасете?
Ответ: Ответ был дан выше на аналогичный вопрос. Также важно учесть, что линейная регрессия использовалась именно для восстановления первоначальной формулы, которая не была указана в описании к датасету, поэтому мы получили близкие к нулю RMSE/MSE/MAE и скорректированный R^2=1 (при восстановлении конкретной формулы это корректные значения, и модель нельзя считать переобученной), поэтому потребности в этом не было.
ПРИМЕРЫ: Пример: Что произойдет, если пользователь не ответит на какой-то из вопросов, т.е. в данных не будет содержаться введенное значение? что будет если ответить на все, кроме ползунков , оставить их все на нуле? Будет ли это засчитываться как не введенное значение?
Ожидаемый результат: Я бы ожидала уведомления-предупреждения, которое бы просило пользователя ответить на все вопросы. При ответе на все вопросы кроме ползунков, также ожидается верификация введенных данных с помощью уведомления, где нужно подтвердить, что 0 - это введенное значение, а не пропуск вопроса.
Ответ: Для бинарных вопросов, в которых пользователь может выбрать только один из двух вариантов ответа, стоит валидация формы, то есть на эти вопросы нельзя не ответить. Для ползунков мы посчитали правильным не делать валидацию, так как не отмеченный вопрос стоит на определенном значении, и мы не можем определить, специально ли пользователь оставил на изначальном значении или случайно. Если каждый раз пользователю, который не выкрутил один ползунок, будет приходить оповещение об этом, это сильно испортит его опыт использования, особенно если он несколько раз проходит анкету, чтобы посмотреть разные результаты.
Пример: Лично мне хотелось бы проверить пример с моей любимой серией игр “FIFA”. Интересно проверить, соотносится ли мое мнение о популярности данной игры с реальной картиной.
Ожидаемый результат: Я ожидаю, что популярность данной игры будет очень большой.
Ответ: Этот вопрос попал к нам ошибочно, речь здесь идёт про другой проект
Пример 1: Все показатели высокие, говорящие о высоком уровне благосостояния, но при этом очень высокий уровень стресса. Ожидаемый результат: Думаю, что будет высокий результат без рекомендаций к улучшению. Но хотелось бы видеть рекомендации к снижению уровня стресса
Пример 2 (объединили два примера): Какой результат я получу, если выберу максимальную оценку по критерию стресса и средние по всем остальным показателям? Ожидаемый результат: Средний показатель благополучия
*Ответ: Благосостояние высчитывается на основе многих показателей, из которых стресс не является решающим. Для лучших ответов с высоким стрессом будет выдан высокий результат благосостояния, для среднего варианта результат получится средним, а так же будут даны рекомендации, но стресс, в сравнении с другими, незначительный фактор, поэтому он даже не будет включен в эту рекомендацию.
Пример: Что будет, если у пользователя нет ответа на вопрос? В приложении нет варианта ответа “Не знаю”. Ожидаемый результат: Пользователь будет вынужден отметить ничего, что будет приравнено к ответу “0”
Ответ: Не очень понятно, как у пользователя может не быть ответа на какой-либо вопрос. Вопросы подобраны так, чтобы быть понятными для всех. Не видим причины, почему пользователь будет вынужден поставить себе 0. Если пользователь совсем не в состоянии ответить на вопрос, он может поставить себе среднее значение, которое не очень сильно повлияет на результат.
Пример: Что будет, если на все вопросы ответить на максимум? (то есть и плохие и хорошие вопросы оценить на максимум)
Ожидаемый результат: Уровень благополучия около 50 Ответ: Результат 80-85, зависит от выбранного ИМТ (лучший ответ ИМТ меньше 25, но непонятно, что в примере максимум для бинарных вопросов)
Пример: Пример, расписанный по номерам вопросов: 1) 3; 2) 2; 3) 8; 4) 5; 5) 10; 6) 3; 7) 4; 8) 1; 9) <25; 10) 9; 11) 6; 12) 4; 13) 5; 14) 8; 15) 0 ; 16) 1; 17) хватает; 18) 10; 19) 10; 20) 5;
Ожидаемый результат: При таких введённых данных я хотел бы получить хотя бы среднюю оценку своего благополучия, а в идеале что-то вроде «выше среднего»
Ответ: Результат 69, то есть действительно выше среднего
Пример: Я хотел бы понимать, что будет с индивидом, который находится в промежуточной ситуации по благополучию - например, ближе к низкому индексу, но не так далеко и от среднего. Насколько точные рекомендации он в таком случае получит?
Ожидаемый результат: Ожидаю, что такой индивид получит персонализированные рекомендации, отражающие специфику его ситуации. Ожидаю, что алгоритм не отнесет человека однозначно к среднему или низкому уровню благополучия, а выберет промежуточную ситуацию.
Ответ: Зависит от того, какие именно характеристики у индивида позволяют говорить, что он ближе к низкому индексу. Будут получены соответствующие рекомендации, и действительно индекс будет средний или чуть ниже среднего. Дело в том, что индекс благополучия – число, и непонятно, что значит “однозначно отнесёт к низкому уровню”.
Пример: Было бы интересно посмотреть на границы между уровням благополучия: низким и средним, средним и высоким.
Ожидаемый результат: Я ожидаю, что при небольшом увеличениизначений каких-то характеристик будет наглядно видно, как смещается уровень благополучия.
Ответ: Не очень понятно, что значит “посмотреть на границы”. Мы не вводим понятия “уровни благополучия”, а лишь выдаём число – результат, индекс. Так что границ нет. При небольшом изменении характеристики не будет очень наглядно видно, как меняется уровень. При большом – да, но это зависит от выбранной характеристики.
Пример: Допустим, что Митрофан только что вышел с экзамена по Микроэкономике, и чтобы доехать до общежития в Дубках ему нужно пройти 12 километров пешком. Он полностью подавлен: не спал, ни с кем не общался, никуда не путешествовал и испытывал стресс каждый день. Он работает системным аналитиком в Теремке - ему это не нравится, но на жизнь хватает
Ожидаемый результат: Критически низкий уровень благополучия и рекомендации обратиться к специалисту (в перспективе - совместно с контактным номером или же ссылкой на сайт с записью на прием)
Ответ: Данный пример хоть и яркий, но дано недостаточно информации, чтобы дать точный ответ. Но на основе приведенных показателей результат действительно крайне низкий.
Пример: В принципе ребята показали все возможные исходы взаимодействия с приложением. Но можно посмотреть, что будет, если пользователь не заполнит все поля опроса.
Ожидаемый результат: Я ожидаю, что приложение выдаст ошибку и попросит заполнить все поля для правильного вывода результата.
Ответ: В данном случае приложение действительно выведет сообщение о неправильно заполненной форме и попросит заполнить все поля и отправить анкету заново
Пример: По вопросам в анкете 1. 2 2. 3 3. 5 4. 5 5. 1 6. 8 7. 4 8. 1 9. 2 10. 7 11. 6 12. 8 13. 4 14. 7 15. 5 16. Меньше 25 17. Не хватает 18. 8 19. 6 20. 6
Ожидаемый результат: Я ожидаю увидеть где-то 6 как уровень благополучия и увидеть рекомендации которые могли бы направить в нужное русло, чтобы повысить результаты
Ответ: Результат 53. В рекомендации указаны моменты, на которые стоит обратить внимание для улучшения результата и повышения уровня благополучия.
Пример: Для половины характеристик выставить максимальный вариант (положительный), для другой – минимальный (отрицательный)
Ожидаемый результат: Это зависит от веса критериев, но, возможно, около 5, как и в приведенном примере с «половинчатыми» характеристиками
Ответ: Верно, при различных выборах характеристик с максимальными и минимальными оценками получатся различные оценки благополучия, которые будут колебаться вокруг 50.
Мы гордимся тем, что создали простое и удобное приложение, с помощью которого человек может получить персонализированную оценку своего благополучия, основанную на научных данных. Во-первых, стоит отметить, что наше приложение имеет простой и удобный интерфейс, не имеющий какого-либо порога для его использования, каждый желающий может им воспользоваться. Во-вторых, в разных частях нашего проекта мы использовали различные методы, алгоритмы и инструменты анализа данных, которые были изучены нами в течение всего майнора: начиная с простого разведочного анализа данных, заканчивая кластерным анализом и различными коэффициентами его оценивания.